/*
 * Copyright 2012-2019 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package sample.projectcustomconfig.customsourceconfig;

import io.spring.initializr.generator.language.CompilationUnit;
import io.spring.initializr.generator.language.SourceCode;
import io.spring.initializr.generator.language.SourceCodeWriter;
import io.spring.initializr.generator.language.TypeDeclaration;
import io.spring.initializr.generator.project.ProjectDescription;
import io.spring.initializr.generator.project.contributor.ProjectContributor;
import io.spring.initializr.generator.spring.util.LambdaSafe;
import org.springframework.beans.factory.ObjectProvider;

import java.io.IOException;
import java.nio.file.Path;
import java.util.List;
import java.util.function.Supplier;
import java.util.stream.Collectors;

/**
 * {@link ProjectContributor} for 自定义源码资源 Contributor
 *
 * @param <T> 特定语言的类型声明
 * @param <C> 特定语言的编译单元
 * @param <S> 特定语言的源代码
 * @author Andy Wilkinson
 * @author Stephane Nicoll
 */
public abstract class CustomSourceCodeProjectContributor<T extends TypeDeclaration, C extends CompilationUnit<T>, S extends SourceCode<T, C>>
        implements ProjectContributor {

    /**
     * 项目描述符(包含某次生成项目的必要的基本信息)
     */
    private final ProjectDescription description;

    /**
     * SourceCode 源码对象，此处使用Supplier函数式接口有一定的
     * 懒加载的意味，当真正去调用contribute生成源码时
     * 才会创建SourceCode的实例
     */
    private final Supplier<S> sourceFactory;

    /**
     * 源码编写器(主要工作是将源码对象转写成具体的类及其内容的工具)
     */
    private final SourceCodeWriter<S> sourceWriter;

    /**
     * 类基本信息提供者
     */
    private final Supplier<ClassInfo> classInfoSupplier;

    /**
     * 方法自定义对象提供者
     * 每一个Customizer包含了类中对应的一个方法的具体内容
     */
    private final ObjectProvider<MethodCustomizer<? extends TypeDeclaration>> methodCustomizers;

    public CustomSourceCodeProjectContributor(ProjectDescription description, Supplier<S> sourceFactory,
                                              SourceCodeWriter<S> sourceWriter, ObjectProvider<MethodCustomizer<?>> methodCustomizers, Supplier<ClassInfo> classInfoSupplier) {
        this.description = description;
        this.sourceFactory = sourceFactory;
        this.sourceWriter = sourceWriter;
        this.methodCustomizers = methodCustomizers;
        this.classInfoSupplier = classInfoSupplier;
    }

    @Override
    public void contribute(Path projectRoot) throws IOException {
        S sourceCode = this.sourceFactory.get();
        ClassInfo classInfo = classInfoSupplier.get();
        String className = classInfo.getClassName();

        //根据类名以及类的所在包信息创建编译单元
        C compilationUnit = sourceCode.createCompilationUnit(this.description.getPackageName() + "." +classInfo.getPackageName(), className);

        //根据生成的编译单元结合类名创建类型声明对象
        //一个具体的类型声明对象中包含方法及属性在内的相关的定义规则
        //换句话讲，是方便我们去给类设置属性及方法的一个个工具
        T classTypeDeclaration = compilationUnit.createTypeDeclaration(className);

        // 定制应用程序方法
        customizeMethod(classTypeDeclaration);

        // 将源码写到项目
        this.sourceWriter.writeTo(
                this.description.getBuildSystem().getMainSource(projectRoot, this.description.getLanguage()),
                sourceCode);
    }

    @SuppressWarnings("unchecked")
    private void customizeMethod(T classTypeDeclaration) {
        List<MethodCustomizer<?>> customizers = this.methodCustomizers.orderedStream().collect(Collectors.toList());
        LambdaSafe.callbacks(MethodCustomizer.class, customizers, classTypeDeclaration)
                .invoke((customizer) -> customizer.customize(classTypeDeclaration));
    }

}
